using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;

namespace Meta
{
	[Serializable]
	public class UnknownNamespaceException : Exception {
		string m_namespaceName;
		public UnknownNamespaceException(string name) {
			m_namespaceName = name;
		}

		public override string Message {
			get {
				return "Unknown namespace "+MissingNamespaceName;
			}
		}

		public string MissingNamespaceName {
			get {
				return m_namespaceName;
			}
		}
	}

	[Serializable]
	public class NativeNamespace : NamespaceBase
	{
		string foreignNamespace;
		//System.Collections.Hashtable bindings = new System.Collections.Hashtable();
		static Hashtable foreignNamespaces = new Hashtable();
		bool builtBindings=false;
		Hashtable exports = new Hashtable();

		public static NativeNamespace Find(string name) {
			return Find(name, true);
		}

		public static NativeNamespace Find(string name, bool checkExists) {
			object n = foreignNamespaces[name];
			NativeNamespace result = null;
			lock (foreignNamespaces) {
				if (n!=null)
					result = (NativeNamespace)n;
				else {
					if (checkExists && !NamespaceExists(name))
						throw new UnknownNamespaceException(name);
					NativeNamespace nw = new NativeNamespace(name);
					foreignNamespaces[name] = nw;
					result = nw;
				}
			}
			return result;
		}

		public NativeNamespace(string name)
		{
			this.name = foreignNamespace = name;
			bindings = new System.Collections.Hashtable();
			System.AppDomain.CurrentDomain.AssemblyLoad +=new AssemblyLoadEventHandler(ScanAssembly);
		}

		void MaybeBuildBindings() {
			if (!builtBindings)
				BuildBindings();
			builtBindings=true;
		}

		void BuildBindings() {
			foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) {
				// We get a problem here if we try to scan an assembly we tried to emit ourselves and the emitting broke.
				// I can't find a way of testing whether an AssemblyBuilder has had its types created yet, so I'm just
				// ignoring all dynamically generated assemblies.
				AssemblyBuilder b = a as AssemblyBuilder;
				if (b==null)
					ScanAssembly(a);
			}
		}

		void ScanAssembly(object sender, AssemblyLoadEventArgs e) {
			// Don't bother scanning this assembly until someone does a lookup.
			if (builtBindings)
				ScanAssembly(e.LoadedAssembly);
		}
		void ScanAssembly(System.Reflection.Assembly a) {
			foreach (Type t in a.GetTypes())
				if (t.FullName.StartsWith(foreignNamespace)) {
					// We found a type in our namespace
					string suffix=t.FullName.Substring(foreignNamespace.Length+1, t.FullName.Length-(foreignNamespace.Length+1));
					int nextDot=suffix.IndexOf('.');
					if (nextDot<0) {
						Symbol sym = Symbol.Intern(suffix);
						// It's a type in our namespace
						if (!bindings.ContainsKey(sym)) {
							// Have to add it now
							lock (this) {
								if (!bindings.ContainsKey(sym)) {     // Check unlikely race condition
									// Add it
									GlobalVariable v = new GlobalVariable(sym, this);
									v.isConstant=true;
									bindings[sym] = v;
									v.Define(t, null);
									exports[v]=v;
								}
							}
						}
					} else {
						// It's a namespace within our namespace
						string namespacename=suffix.Substring(0,nextDot);
						Symbol nsym=Symbol.Intern(namespacename);
						if (!bindings.ContainsKey(nsym))
							// Have to add it now
							lock (this) {
								if (!bindings.ContainsKey(nsym)) {     // Check unlikely race condition
									// Add it
									GlobalVariable v = new GlobalVariable(nsym, this);
									v.isConstant=true;
									bindings[nsym] = v;
									v.Define(NativeNamespace.Find(foreignNamespace+"."+namespacename, false), null);
									exports[v]=v;
								}
							}
					}
				}
		}

		static bool NamespaceExists(string name) {
			string extendedName = name + ".";
			// Very painful
			foreach (System.Reflection.Assembly a in AppDomain.CurrentDomain.GetAssemblies()) {
				foreach (Type t in a.GetTypes()) {
					if (t.FullName.StartsWith(extendedName))
						return true;
				}
			}
			return false;
		}

		public override Expression Parse(object list, Parser subparser) {
			Symbol s = list as Symbol;

			// If we get here, then they've typed something of the form [namespace-name args ...], which is an error.
			if (s==null)
				throw new SyntaxError("Can't execute complex expressions within native namespaces", list);
			GlobalVariable v = Lookup(s, true, true, true);

			if (v==null)
				throw new UndefinedVariableException(new GlobalVariable(s, this));
			return v;
		}

		public override GlobalVariable Lookup(Symbol sym, bool searchImports, bool create, bool includePrivate) {
			MaybeBuildBindings();
			object keys=bindings.Keys;
			object binding=bindings[sym];
			return (GlobalVariable)bindings[sym];
#if oldNNlookup
			lock (this) {
				if (bindings.ContainsKey(sym))
					return (GlobalVariable)bindings[sym];

				string objectName = foreignNamespace+"."+sym.name;
				Type probe = Utilities.SearchForType(objectName);
				if (probe != null || create) {
					GlobalVariable v = new GlobalVariable(sym, this);
					v.isConstant = true;
					bindings[sym] = v;

					v.Define((probe==null)?((object)(NativeNamespace.Find(objectName))):(object)probe, null);
					return v;
				} else
					return null;
			}
#endif
		}

		public override void Export(Symbol s) {
		}

		public override IDictionary Exports {
			get {
				MaybeBuildBindings();
				return exports;
			}
		}


		public override void Import(NamespaceBase n) {
			throw new Exception("Native namespaces cannot import");
		}
	}
}
